#ifndef MS3D_H_INCLUDED
#define MS3D_H_INCLUDED

#include <vector>
#include <fstream>
#include <string>

#include "../math/vec4.h"
#include "../math/vec3.h"
#include "../math/mat4.h"

using std::vector;
using std::ifstream;
using std::string;

#include "animatedmodel.h"

namespace sheeplib {

    namespace model {

		typedef unsigned char 	byte;
		typedef unsigned short 	word;

#pragma pack(push)  /* push current alignment to stack */
#pragma pack(1)     /* set alignment to 1 byte boundary */

        struct MS3DHeader {
            char    id[10];                                     // always "MS3D000000"
            int     version;                                    // 4
        };

        struct MS3DVertex {
            byte  m_Flag; ///< Some kind of flag
            slVec3  m_Pos; ///< The vertex position
            char  m_BoneID;  ///< The boneID (if any)
            byte  m_Reference; ///< Reference.... I dunno
        };

        struct MS3DTriangle {
            word m_Flag; ///< again... i dunno
            word m_Index[3]; ///< Vertex indices
            slVec3 m_Normals[3]; ///< Vertex normals
            float m_S[3];	///< Why didnt they do this in (s, t) pairs?
            float m_T[3];
            byte m_SmoothingGroup;
            byte m_IndexGroup;
        };

        struct MS3DMaterial {
            char 	m_Name[32];
            slVec4	m_Ambient;
            slVec4	m_Diffuse;
            slVec4	m_Specular;
            slVec4	m_Emissive;
            float	m_Shininess;
            float 	m_Transparency;
            byte 	m_Mode; ///< Unused
            char	m_Texture[128]; ///< Texture name
            char 	m_Alphamap[128]; ///< AlphaMap name
        };

        struct MS3DAnimationInfo {
            float m_FramesPerSecond; ///< No point commenting this really :p
            float m_CurrentTime; ///< um? currently selected frame?
            int	  m_TotalFrames; ///< Yes that IS a full blown 4 byte integer, about time!
        };

        struct MS3DKeyFrameRotation {
            float m_Time;
            slVec3 m_Rotation;
        };

        struct MS3DKeyFramePosition {
            float m_Time;
            slVec3 m_Position;
        };

#pragma pack(pop)

        struct MS3DGroup {
            byte 	m_Flag; ///< More flags, still dunno what they are
            char 	m_Name[32]; ///< Name of this group
            word 	m_NumTriangles; ///< Number of triangles in this group
            vector<word>  m_TriangleIndices; ///< This is an array of m_NumTriangles - 1
            char    m_MaterialIndex; ///< If this is -1 then there is no material

			MS3DGroup()
			:m_Flag(0), m_NumTriangles(0) {	}

			~MS3DGroup() {
				m_TriangleIndices.clear();
			}

			MS3DGroup(const MS3DGroup& g) {
				Assign(g);
			}

			MS3DGroup& operator=(const MS3DGroup& g) {
				Assign(g);
				return *this;
			}

			void Assign(const MS3DGroup& j) {
				m_Flag = j.m_Flag;
				memcpy(m_Name, j.m_Name, sizeof(char)*32);
				m_NumTriangles = j.m_NumTriangles;
				m_TriangleIndices.assign(j.m_TriangleIndices.begin(), j.m_TriangleIndices.end());
				m_MaterialIndex = j.m_MaterialIndex;
			}
        };

        struct MS3DJoint {
            byte m_Flags; ///< Yes even more flags, this apparently is SELECTED | DIRTY
            char m_Name[32]; ///< Name of the joint
            char m_ParentName[32]; ///< Name of the parent joint
            slVec3 m_Rotation;
            slVec3 m_Position;

            word m_NumKeyframesRotation; ///< These are the sizes of the following 2 arrays
            word m_NumKeyframesPosition;

            vector<MS3DKeyFrameRotation> m_KeyframesRotation;
            vector<MS3DKeyFramePosition> m_KeyframesPosition;

            slMat4 m_Relative;
            slMat4 m_Absolute;
            slMat4 m_Final;

			MS3DJoint() {
				m_KeyframesRotation.clear();
				m_KeyframesPosition.clear();
			}

			MS3DJoint(const MS3DJoint& j) {
				Assign(j);
			}

			MS3DJoint& operator=(const MS3DJoint&j) {
				Assign(j);
				return *this;
			}

			~MS3DJoint() {
				m_KeyframesRotation.clear();
				m_KeyframesPosition.clear();
			}

			void Assign(const MS3DJoint& j) {
				m_Flags = j.m_Flags;
				memcpy(m_Name, j.m_Name, sizeof(char)*32);
				memcpy(m_ParentName, j.m_ParentName, sizeof(char)*32);
				m_Rotation = j.m_Rotation;
				m_Position = j.m_Position;
				m_NumKeyframesPosition = j.m_NumKeyframesPosition;
				m_NumKeyframesRotation = j.m_NumKeyframesRotation;
				m_KeyframesRotation.assign(j.m_KeyframesRotation.begin(), j.m_KeyframesRotation.end());
				m_KeyframesPosition.assign(j.m_KeyframesPosition.begin(), j.m_KeyframesPosition.end());

				slMat4Assign(&m_Absolute, &j.m_Absolute);
				slMat4Assign(&m_Relative, &j.m_Relative);
			}

        };



        class MS3D : public AnimatedModel {
            private:
				bool CheckFileHeaderIsValid(ifstream& fileIn);

				bool ReadVertices(ifstream& fileIn, vector<MS3DVertex>& vertices, word numVertices);
				bool ReadTriangles(ifstream& fileIn, vector<MS3DTriangle>& triangles, word numTriangles);
				bool ReadGroups(ifstream& fileIn, vector<MS3DGroup>& groups, word numGroups);
				bool ReadMaterials(ifstream& fileIn, vector<MS3DMaterial>& materials, word numMaterials);
				bool ReadJoints(ifstream& fileIn, vector<MS3DJoint>& joints, word numJoints);

				vector<MS3DJoint> m_Joints; //We need the joints to animate the MS3D

				void SetupJoints(vector<MS3DVertex>& vertices, vector<MS3DTriangle>& triangles);
				int FindBoneByName(const string& name);

				void ApplyTransformations();

				float CalculateInterpolation(float currentTime, float prevTime, float nextTime);

				bool StoreInInternalFormat(const vector<MS3DVertex>& vertices, 
										 const vector<MS3DGroup>& groups, 
										 const vector<MS3DTriangle>& triangles, 
										 const vector<MS3DMaterial>& materials);

				void SetFrameTime(float time);

				template <typename T>
				std::pair<T*, T*> FindNearestKeyFrames(float time, vector<T>& keyframes) {
					//If the time is less than the first time then just return the first 2 frames
					if (time <= keyframes[0].m_Time) {
						return std::make_pair(&keyframes[0], &keyframes[1]);
					}

					typedef typename vector<T>::iterator KeyframeIter;
					for (KeyframeIter it = keyframes.begin(); it != keyframes.end(); ++it)
					{
						KeyframeIter next = it + 1;

						if (next == keyframes.end())
							break;

						if ((*it).m_Time <= time && (*next).m_Time > time) {
							return std::make_pair(&(*it), &(*next));
						}
					}

					throw std::logic_error("Could not find keyframes matching the time specified!");
				}

            protected:
                virtual bool LoadModelFile(const string& filename);
				virtual void BasicGLRender() const;
				virtual void RenderGroup(unsigned int groupID) const;
				virtual void Update(float dT);
			public:
                MS3D();
                virtual ~MS3D();
                
                void slBasicRender() const;
                void slRenderSubset(unsigned int i) const;
        };
    }
}

#endif // MS3D_H_INCLUDED
